package com.sunday.authorization.security.authentication;

import com.sunday.authorization.security.constants.SecurityConstants;
import com.sunday.common.core.asserts.BusinessAssert;
import com.sunday.common.core.enums.ErrorCodeEnum;
import com.sunday.common.core.exception.BusinessException;
import com.sunday.redis.lua.core.RedisLuaTemplate;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.lang.Nullable;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

import java.util.Optional;
import java.util.stream.Stream;

/**
 * 自定义用户登录，需要验证图片验证码和手机验证码
 *
 * @author sunday
 * @since 2024/9/14
 */
@Slf4j
public class CustomUsernamePasswordAuthenticationFilter extends UsernamePasswordAuthenticationFilter {

    public static final String SPRING_SECURITY_FORM_VERIFY_CODE_KEY = "verifyCode";

    public static final String SPRING_SECURITY_FORM_CAPTCHA_KEY = "captcha";

    private String verifyCodeParameter = SPRING_SECURITY_FORM_VERIFY_CODE_KEY;

    private String captchaParameter = SPRING_SECURITY_FORM_CAPTCHA_KEY;

    private RedisLuaTemplate redisLuaTemplate;

//    public CustomUsernamePasswordAuthenticationFilter(AuthenticationManager authenticationManager, RedisLuaTemplate redisLuaTemplate) {
//        super.setAuthenticationManager(authenticationManager);
//        this.redisLuaTemplate = redisLuaTemplate;
//    }

    public CustomUsernamePasswordAuthenticationFilter(RedisLuaTemplate redisLuaTemplate) {
        this.redisLuaTemplate = redisLuaTemplate;
    }

    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
            throws AuthenticationException {

        String username = obtainUsername(request);
        username = (username != null) ? username.trim() : "";

        try {

            /**
             * 优先判定用户登录错误次数是否已经超限
             */
            Long currCount = redisLuaTemplate.get(SecurityConstants.loginErrorMaxCountKey(username), Long.class);
            BusinessAssert.isTrue(currCount == null || currCount < SecurityConstants.LOGIN_ERROR_MAX_COUNT_NUMBER, ErrorCodeEnum.A0_200, () -> "用户输入错误次数超限");

            /**
             * 图片验证码 验证
             */
            Optional.ofNullable(request.getCookies())
                    .flatMap(cookies -> Stream.of(cookies)
                            .filter(cookie -> SecurityConstants.COOKIE_VERIFY_CODE_KEY.equals(cookie.getName()))
                            .findFirst()
                            .map(cookie -> cookie.getValue())
                    )
                    .map(sequenceId -> redisLuaTemplate.get(SecurityConstants.loginImageVerifyCode(sequenceId)))
                    .filter(verifyCode -> verifyCode != null && verifyCode.equals(obtainVerifyCode(request)))
                    .orElseThrow(() -> new BusinessException(ErrorCodeEnum.A0_240, "用户图片验证码错误"));

            /**
             * 手机验证码 验证
             */
            Optional.ofNullable(redisLuaTemplate.get(SecurityConstants.loginPhoneCaptcha(username)))
                    .filter(captcha -> captcha.equals(obtainCaptcha(request)))
                    // A0_240(ErrorClassifyEnum.A + "0240","用户验证码错误"),
                    .orElseThrow(() -> new BusinessException(ErrorCodeEnum.A0_240, "用户手机验证码错误"));

            Authentication authentication = super.attemptAuthentication(request, response);
            log.info("验证登录成功: {}", authentication);
            return authentication;

        } catch (Exception cause) {

            log.info("自定义登录流程失败 {}", cause.toString());

            if (cause instanceof BusinessException || cause instanceof BadCredentialsException) {
                //异常处理
                String append = "";

                long count = redisLuaTemplate.incrWithinPeriod(SecurityConstants.loginErrorMaxCountKey(username), 7200L);
                // 当前失败次数 <= 10 && >= 6
                if (count <= SecurityConstants.LOGIN_ERROR_MAX_COUNT_NUMBER && count >= SecurityConstants.LOGIN_ERROR_WARN_COUNT_NUMBER) {
                    append = STR."您还有\{SecurityConstants.LOGIN_ERROR_MAX_COUNT_NUMBER - count}次尝试机会";
                }

                if (cause instanceof BadCredentialsException badCredentialsException) {
                    throw new CustomAuthenticationException(ErrorCodeEnum.A0_210.errorCode, "用户账户或密码错误", append, true);
                }

                /**
                 * 符合业务级别异常，将进行额外的计数处理
                 */
                if (cause instanceof BusinessException businessException) {
                    // A0_221(ErrorClassifyEnum.A + "0221","账户或密码错误"),
                    // 当前失败次数 <= 10 && >= 6
                    BusinessAssert.state(count <= SecurityConstants.LOGIN_ERROR_MAX_COUNT_NUMBER && count >= SecurityConstants.LOGIN_ERROR_WARN_COUNT_NUMBER, () -> {
                        throw new CustomAuthenticationException(businessException.getCode(), businessException.getMessage(), STR."您还有\{SecurityConstants.LOGIN_ERROR_MAX_COUNT_NUMBER - count}次尝试机会", true);
                    });

                    throw new CustomAuthenticationException(businessException.getCode(), businessException.getMessage());
                }
            }

            throw cause;

        }
    }


    @Nullable
    protected String obtainVerifyCode(HttpServletRequest request) {
        return request.getParameter(this.verifyCodeParameter);
    }

    @Nullable
    protected String obtainCaptcha(HttpServletRequest request) {
        return request.getParameter(this.captchaParameter);
    }

}
